package com.example.modelfunction.shrio.jwt;



import com.example.modelfunction.shrio.usermanager.mode.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import org.bouncycastle.util.encoders.Hex;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class Jwt {
    /**
     * 创建jwt
     *
     * @param id
     * @param subject
     * @param ttlMillis 过期的时间长度
     * @return
     * @throws Exception
     */
    private static final String CLAIM_KEY_USER_ID = "uid";
    private static final String CLAIM_KEY_ACCOUNT = "user";
    private String customToken;
    private String jwtToken;
    private String mToken;
    private boolean hasParse;
    Claims claims;

    public Jwt() {

    }

    public Jwt(String token) {
        this.mToken = token;


    }

    public Jwt(Claims claims) {
        this.claims = claims;
    }

    public static String generateAccessToken(User user, long ttlMillis) throws Exception {
        io.jsonwebtoken.SignatureAlgorithm signatureAlgorithm = io.jsonwebtoken.SignatureAlgorithm.HS256; //指定签名的时候使用的签名算法，也就是header那部分，jjwt已经将这部分内容封装好了。
        long nowMillis = System.currentTimeMillis();//生成JWT的时间
        Date now = new Date(nowMillis);
        Map<String, Object> claims = new HashMap<String, Object>();//创建payload的私有声明（根据特定的业务需要添加，如果要拿这个做验证，一般是需要和jwt的接收方提前沟通好验证方式的）
        claims.put(CLAIM_KEY_USER_ID, user.getId());
        claims.put(CLAIM_KEY_ACCOUNT, user.getAccount());
        SecretKey key = generalKey(user);//生成签名的时候使用的秘钥secret,这个方法本地封装了的，一般可以从本地配置文件中读取，切记这个秘钥不能外露哦。它就是你服务端的私钥，在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        //下面就是在为payload添加各种标准声明和私有声明了
        JwtBuilder builder = Jwts.builder() //这里其实就是new一个JwtBuilder，设置jwt的body
                .setClaims(claims)          //如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
                .setId(user.getId().toString())                  //设置jti(JWT ID)：是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
                .setIssuedAt(now)           //iat: jwt的签发时间
                .setSubject(user.getPwd() + user.getRoleId())        //sub(Subject)：代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志。
                .signWith(signatureAlgorithm, key);//设置签名使用的签名算法和签名使用的秘钥
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);     //设置过期时间
        }
        String hexcode = new String(Hex.encode(user.getId().toString().getBytes("utf-8")), "utf-8");
        String token = hexcode + "." + builder.compact();
        return token;           //就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
        //打印了一哈哈确实是下面的这个样子
        //eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJEU1NGQVdEV0FEQVMuLi4iLCJzdWIiOiIiLCJ1c2VyX25hbWUiOiJhZG1pbiIsIm5pY2tfbmFtZSI6IkRBU0RBMTIxIiwiZXhwIjoxNTE3ODI4MDE4LCJpYXQiOjE1MTc4Mjc5NTgsImp0aSI6Imp3dCJ9.xjIvBbdPbEMBMurmwW6IzBkS3MPwicbqQa2Y5hjHSyo
    }

    /**
     * 解密jwt
     *
     * @param user
     * @return
     * @throws Exception
     */
    public Jwt parseJWT(User user) throws Exception {
        parseTokenString();
        if (customToken != null) {
            SecretKey key = generalKey(user);  //签名秘钥，和生成的签名的秘钥一模一样
            claims = Jwts.parser()  //得到DefaultJwtParser
                    .setSigningKey(key)         //设置签名的秘钥
                    .parseClaimsJws(jwtToken).getBody();//设置需要解析的jwt
        }
        return this;
    }

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey(User user) {
        SecretKey key = new SecretKeySpec(user.getPwd().getBytes(), 0, user.getPwd().length(), "AES");// 根据给定的字节数组使用AES加密算法构造一个密钥，使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。（后面的文章中马上回推出讲解Java加密和解密的一些算法）
        return key;
    }

    public static Date getCreatedDateFromToken(Claims claims) {
        Date created = claims.getIssuedAt();
        return created;
    }

    public Date getExpirationDateFromToken(Claims claims) {
        Date expiration = claims.getExpiration();
        return expiration;
    }

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

    public Boolean canTokenBeRefreshed(Date lastPasswordReset) {
        final Date created = claims.getExpiration();
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && (!isTokenExpired());
    }

    private Boolean isTokenExpired() {
        Date expiration = claims.getExpiration();
        return expiration.before(new Date());
    }

    public Boolean validateToken(User user) {
        if (!hasParse)
            parseTokenString();
        if (customToken == null||user==null)
            return false;
        SecretKey key = generalKey(user);  //签名秘钥，和生成的签名的秘钥一模一样
        claims = Jwts.parser()  //得到DefaultJwtParser
                .setSigningKey(key)         //设置签名的秘钥
                .parseClaimsJws(jwtToken).getBody();//设置需要解析的jwt
        return claims.getSubject() != null
                && claims.getSubject().equals(user.getPwd() + user.getRoleId())
                && claims.getId().equals(user.getId().toString())
                && user.getAccount().equals(claims.get(CLAIM_KEY_ACCOUNT));


    }
    public String getAccount()
    {
        if(claims!=null)
            return (String)claims.get(CLAIM_KEY_ACCOUNT);
        else
            return null;
    }
    public Boolean isValidateParse() {
        if (!hasParse)
            parseTokenString();
        return (customToken != null);
    }

    public String getJwtToken() {
        return jwtToken;
    }

    public Long parseTokenString() {
        Long res = null;
        if (!hasParse) {
            hasParse = true;
            int index = mToken.indexOf('.');
            String key = new String(mToken.substring(0, index));
            String jwtstr = mToken.substring(index + 1);
            String mvalue = null;
            try {
                mvalue = new String(Hex.decode(key), "utf-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            customToken = mvalue;
            jwtToken = jwtstr;
            res = Long.parseLong(customToken);
        }
        return res;
    }

}
